/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.mapbox.ohos.gestures;


import ohos.app.Context;
import ohos.multimodalinput.event.TouchEvent;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

/**
 * @since 2021-06-29
 */
public class OhosGesturesManager {

    /**
     * GESTURE_TYPE_SCROLL
     */
    public static final int GESTURE_TYPE_SCROLL = 0;
    /**
     * GESTURE_TYPE_SCALE
     */
    public static final int GESTURE_TYPE_SCALE = 1;
    /**
     * GESTURE_TYPE_ROTATE
     */
    public static final int GESTURE_TYPE_ROTATE = 2;
    /**
     * GESTURE_TYPE_SHOVE
     */
    public static final int GESTURE_TYPE_SHOVE = 3;
    /**
     * GESTURE_TYPE_MULTI_FINGER_TAP
     */
    public static final int GESTURE_TYPE_MULTI_FINGER_TAP = 4;
    /**
     * GESTURE_TYPE_SINGLE_TAP_UP
     */
    public static final int GESTURE_TYPE_SINGLE_TAP_UP = 5;
    /**
     * GESTURE_TYPE_LONG_PRESS
     */
    public static final int GESTURE_TYPE_LONG_PRESS = 6;
    /**
     * GESTURE_TYPE_FLING
     */
    public static final int GESTURE_TYPE_FLING = 7;
    /**
     * GESTURE_TYPE_SHOW_PRESS
     */
    public static final int GESTURE_TYPE_SHOW_PRESS = 8;
    /**
     * GESTURE_TYPE_DOWN
     */
    public static final int GESTURE_TYPE_DOWN = 9;
    /**
     * GESTURE_TYPE_DOUBLE_TAP
     */
    public static final int GESTURE_TYPE_DOUBLE_TAP = 10;
    /**
     * GESTURE_TYPE_DOUBLE_TAP_EVENT
     */
    public static final int GESTURE_TYPE_DOUBLE_TAP_EVENT = 11;
    /**
     * GESTURE_TYPE_SINGLE_TAP_CONFIRMED
     */
    public static final int GESTURE_TYPE_SINGLE_TAP_CONFIRMED = 12;
    /**
     * GESTURE_TYPE_MOVE
     */
    public static final int GESTURE_TYPE_MOVE = 13;
    /**
     * GESTURE_TYPE_SIDEWAYS_SHOVE
     */
    public static final int GESTURE_TYPE_SIDEWAYS_SHOVE = 14;
    /**
     * GESTURE_TYPE_QUICK_SCALE
     */
    public static final int GESTURE_TYPE_QUICK_SCALE = 15;

    private final List<Set<Integer>> mutuallyExclusiveGestures = new ArrayList<>();
    private final List<BaseGesture> detectors = new ArrayList<>();

    private final StandardGestureDetector standardGestureDetector;
    private final StandardScaleGestureDetector standardScaleGestureDetector;
    private final RotateGestureDetector rotateGestureDetector;
    private final ShoveGestureDetector shoveGestureDetector;
    private final MultiFingerTapGestureDetector multiFingerTapGestureDetector;
    private final MoveGestureDetector moveGestureDetector;
    private final SidewaysShoveGestureDetector sidewaysShoveGestureDetector;

    /**
     * Creates a new instance of the {@link OhosGesturesManager}.
     *
     * @param context activity's context
     */
    public OhosGesturesManager(Context context) {
        this(context, true);
    }

    /**
     * Creates a new instance of the {@link OhosGesturesManager}.
     *
     * @param context activity's context
     * @param applyDefaultThresholds if true, default gestures thresholds and adjustments will be applied
     */
    public OhosGesturesManager(Context context, boolean applyDefaultThresholds) {
        this(context, new ArrayList<Set<Integer>>(), applyDefaultThresholds);
    }

    @SafeVarargs
    public OhosGesturesManager(Context context, Set<Integer>... exclusiveGestures) {
        this(context, Arrays.asList(exclusiveGestures), true);
    }

    public OhosGesturesManager(Context context, List<Set<Integer>> exclusiveGestures, boolean applyDefaultThresholds) {
        this.mutuallyExclusiveGestures.addAll(exclusiveGestures);

        rotateGestureDetector = new RotateGestureDetector(context, this);
        standardScaleGestureDetector = new StandardScaleGestureDetector(context, this);
        shoveGestureDetector = new ShoveGestureDetector(context, this);
        sidewaysShoveGestureDetector = new SidewaysShoveGestureDetector(context, this);
        multiFingerTapGestureDetector = new MultiFingerTapGestureDetector(context, this);
        moveGestureDetector = new MoveGestureDetector(context, this);
        standardGestureDetector = new StandardGestureDetector(context, this);

        detectors.add(rotateGestureDetector);
        detectors.add(standardScaleGestureDetector);
        detectors.add(shoveGestureDetector);
        detectors.add(sidewaysShoveGestureDetector);
        detectors.add(multiFingerTapGestureDetector);
        detectors.add(moveGestureDetector);
        detectors.add(standardGestureDetector);

        if (applyDefaultThresholds) {
            initDefaultThresholds();
        }
    }

    private void initDefaultThresholds() {
        for (BaseGesture detector : detectors) {
            if (detector instanceof MultiFingerGesture) {
                ((MultiFingerGesture) detector).setSpanThresholdResource(ResourceTable.Float_mapbox_internalMinSpan24);
            }

            if (detector instanceof StandardScaleGestureDetector) {
                ((StandardScaleGestureDetector) detector).setSpanSinceStartThresholdResource(
                    ResourceTable.Float_mapbox_defaultScaleSpanSinceStartThreshold);
            }

            if (detector instanceof ShoveGestureDetector) {
                ((ShoveGestureDetector) detector).setPixelDeltaThresholdResource(ResourceTable.Float_mapbox_defaultShovePixelThreshold);
                ((ShoveGestureDetector) detector).setMaxShoveAngle(Constants.DEFAULT_SHOVE_MAX_ANGLE);
            }

            if (detector instanceof SidewaysShoveGestureDetector) {
                ((SidewaysShoveGestureDetector) detector).setPixelDeltaThresholdResource(
                    ResourceTable.Float_mapbox_defaultShovePixelThreshold);
                ((SidewaysShoveGestureDetector) detector).setMaxShoveAngle(Constants.DEFAULT_SHOVE_MAX_ANGLE);
            }

            if (detector instanceof MultiFingerTapGestureDetector) {
                ((MultiFingerTapGestureDetector) detector).setMultiFingerTapMovementThresholdResource(
                    ResourceTable.Float_mapbox_defaultMultiTapMovementThreshold);

                ((MultiFingerTapGestureDetector) detector).setMultiFingerTapTimeThreshold(
                    Constants.DEFAULT_MULTI_TAP_TIME_THRESHOLD);
            }

            if (detector instanceof RotateGestureDetector) {
                ((RotateGestureDetector) detector).setAngleThreshold(Constants.DEFAULT_ROTATE_ANGLE_THRESHOLD);
            }
        }
    }

    /**
     * Passes motion events to all gesture detectors.
     *
     * @param motionEvent event。
     * @return true if the touch event is handled by any gesture, false otherwise.
     */
    public boolean onTouchEvent(TouchEvent motionEvent) {
        boolean isHandled = false;
        for (BaseGesture detector : detectors) {
            if (detector.onTouchEvent(motionEvent)) {
                isHandled = true;
            }
        }
        return isHandled;
    }

    /**
     * setStandardGestureListener
     *
     * @param listener your gestures listener
     */
    public void setStandardGestureListener(StandardGestureDetector.StandardOnGestureListener listener) {
        standardGestureDetector.setListener(listener);
    }

    /**
     * Removes a listener for all the events normally returned by the
     */
    public void removeStandardGestureListener() {
        standardGestureDetector.removeListener();
    }

    /**
     * Sets a listener for scale gestures.
     *
     * @param listener your gestures listener
     */
    public void setStandardScaleGestureListener(StandardScaleGestureDetector.StandardOnScaleGestureListener listener) {
        standardScaleGestureDetector.setListener(listener);
    }

    /**
     * Removes a listener for scale gestures.
     */
    public void removeStandardScaleGestureListener() {
        standardScaleGestureDetector.removeListener();
    }

    /**
     * Sets a listener for rotate gestures.
     *
     * @param listener your gestures listener
     */
    public void setRotateGestureListener(RotateGestureDetector.OnRotateGestureListener listener) {
        rotateGestureDetector.setListener(listener);
    }

    /**
     * Removes a listener for rotate gestures.
     */
    public void removeRotateGestureListener() {
        rotateGestureDetector.removeListener();
    }

    /**
     * Sets a listener for shove gestures.
     *
     * @param listener your gestures listener
     */
    public void setShoveGestureListener(ShoveGestureDetector.OnShoveGestureListener listener) {
        shoveGestureDetector.setListener(listener);
    }

    /**
     * Removes a listener for shove gestures.
     */
    public void removeShoveGestureListener() {
        shoveGestureDetector.removeListener();
    }

    /**
     * Sets a listener for multi finger tap gestures.
     *
     * @param listener your gestures listener
     */
    public void setMultiFingerTapGestureListener(MultiFingerTapGestureDetector.OnMultiFingerTapGestureListener listener) {
        multiFingerTapGestureDetector.setListener(listener);
    }

    /**
     * Removes a listener for multi finger tap gestures.
     */
    public void removeMultiFingerTapGestureListener() {
        multiFingerTapGestureDetector.removeListener();
    }

    /**
     * Sets a listener for move gestures.
     * <p>
     * {@link MoveGestureDetector} serves similar purpose to
     * #onScroll(MotionEvent, MotionEvent, float, float)}, however, it's a {@link ProgressiveGesture} that
     * introduces {@link MoveGestureDetector.OnMoveGestureListener#onMoveBegin(MoveGestureDetector)},
     * {@link MoveGestureDetector.OnMoveGestureListener#onMoveEnd(MoveGestureDetector, float, float)},
     * threshold with {@link MoveGestureDetector#setMoveThreshold(float)} and multi finger support thanks to
     * {@link MoveDistancesObject}.
     *
     * @param listener your gestures listener
     */
    public void setMoveGestureListener(MoveGestureDetector.OnMoveGestureListener listener) {
        moveGestureDetector.setListener(listener);
    }

    /**
     * Removes a listener for move gestures.
     */
    public void removeMoveGestureListener() {
        moveGestureDetector.removeListener();
    }

    /**
     * Sets a listener for sideways shove gestures.
     *
     * @param listener your gestures listener
     */
    public void setSidewaysShoveGestureListener(SidewaysShoveGestureDetector.OnSidewaysShoveGestureListener listener) {
        sidewaysShoveGestureDetector.setListener(listener);
    }

    /**
     * Removes a listener for sideways shove gestures.
     */
    public void removeSidewaysShoveGestureListener() {
        sidewaysShoveGestureDetector.removeListener();
    }

    /**
     * Get a list of all active gesture detectors.
     *
     * @return list of all gesture detectors
     */
    public List<BaseGesture> getDetectors() {
        return detectors;
    }

    /**
     * getStandardGestureDetector
     *
     * @return gesture detector
     */
    public StandardGestureDetector getStandardGestureDetector() {
        return standardGestureDetector;
    }

    /**
     * Get scale gesture detector.
     *
     * @return gesture detector
     */
    public StandardScaleGestureDetector getStandardScaleGestureDetector() {
        return standardScaleGestureDetector;
    }

    /**
     * Get rotate gesture detector.
     *
     * @return gesture detector
     */
    public RotateGestureDetector getRotateGestureDetector() {
        return rotateGestureDetector;
    }

    /**
     * Get shove gesture detector.
     *
     * @return gesture detector
     */
    public ShoveGestureDetector getShoveGestureDetector() {
        return shoveGestureDetector;
    }

    /**
     * Get multi finger tap gesture detector.
     *
     * @return gesture detector
     */
    public MultiFingerTapGestureDetector getMultiFingerTapGestureDetector() {
        return multiFingerTapGestureDetector;
    }

    /**
     * Get move gesture detector.
     *
     * @return gesture detector
     */
    public MoveGestureDetector getMoveGestureDetector() {
        return moveGestureDetector;
    }

    /**
     * Get sideways shove gesture detector.
     *
     * @return gesture detector
     */
    public SidewaysShoveGestureDetector getSidewaysShoveGestureDetector() {
        return sidewaysShoveGestureDetector;
    }

    /**
     * Sets a number of sets containing mutually exclusive gestures.
     *
     * @param exclusiveGestures a number of sets of that <b>should not</b> be invoked at the same.
     * This means that when a set contains a {@link ProgressiveGesture} and this gestures
     * is in progress no other gestures from the set will be invoked.
     * <p>
     * At the moment {@link #GESTURE_TYPE_SCROLL} is not interpreted as a progressive gesture
     * because it is not interpreted this way by the
     */
    @SafeVarargs
    public final void setMutuallyExclusiveGestures(Set<Integer>... exclusiveGestures) {
        setMutuallyExclusiveGestures(Arrays.asList(exclusiveGestures));
    }

    /**
     * Sets a list of sets containing mutually exclusive gestures.
     *
     * @param exclusiveGestures a list of sets of that <b>should not</b> be invoked at the same.
     * This means that when a set contains a {@link ProgressiveGesture} and this gestures
     * is in progress no other gestures from the set will be invoked.
     * <p>
     * At the moment {@link #GESTURE_TYPE_SCROLL} is not interpreted as a progressive gesture
     * because it is not interpreted this way by the
     */
    public void setMutuallyExclusiveGestures(List<Set<Integer>> exclusiveGestures) {
        this.mutuallyExclusiveGestures.clear();
        this.mutuallyExclusiveGestures.addAll(exclusiveGestures);
    }

    /**
     * Returns a list of sets containing mutually exclusive gestures.
     *
     * @return mutually exclusive gestures
     * @see #setMutuallyExclusiveGestures(List)
     */
    public List<Set<Integer>> getMutuallyExclusiveGestures() {
        return mutuallyExclusiveGestures;
    }

}
